﻿/* Beinhaltet unterschiedliche jQuery Plugins:
1. quickSearch -> Filtert Tabellen Zeile für Zeile nach einem eingegebenen Suchbegriff und zeigt nur die Zeilen an die dem Suchbegriff entsprechen
--> z.B. aufzurufen mit:  $('.tableClass').quickSearch(); 
--> Für Parameter die übergeben werden können bitte direkt im Plugin schauen "defaultOptions"
--> Die Tabelle / Tabellen die gefiltert werden sollen müssen einen "thead" und "tbody" enthalten, sonst funktioniert die Suche nicht.
--> Außerdem muss ein Control mit der id "qsearch" existieren, in diesem Control wird das Input erstellt in der die Suche eingegeben werden kann (die ID kann auch über die Optionen geändert werden)

2. clearInputText -> Erstellt bei jedem input feld welches über den Selector beim Aufrufen des Plugins übergeben wird ein "X", mit dem man den Inhalt des Input feldes löschen kann.
--> z..B. aufzurufen mit: $('input').clearInputText();
--> Für Parameter die übergeben werden können bitte direkt im Plugin schauen "defaultOptions"
--> Achtung für das Input feld wird das Padding Right um den Wert "+crossPositionLeft" + 5 gesetzt

min Version gepackt mit: http://dean.edwards.name/packer/
*/

//QUICKSEARCH PLUGIN
(function ($, window, document, undefined) {

    //Damit das ganze funktioniert, muss die Tabelle einen thead und einen tbody element haben
    $.fn.quickSearch = function (options) {
        var defaultsOptions = {
            searchField: '#qsearch', //der Container in dem das Inputfeld für die Suche erstellt wird
            searchFieldId: 'searchbox',
            searchFieldName: 'Schnellsuche', //wenn angeben, dann wird noch ein Label erstellt mit dem Namen
            searchfieldPlaceholderText: "Tabellenfilter eingeben",
            defaultQuery: '',
            minSearchLength: 3,
            ClearTableRowText: 'Es wurden keine Übereinstimmungen mit dem Schnellfilter gefunden.'
        },
            config = $.extend(defaultsOptions, options),
            currentUrl = document.URL.split('?')[0], //die aktuelle URL, wird genommen als Key für den LocalStorage um den Searchfield Value abzulegen
            timer,
            rows,
            anzErgebnisseLabel,
            clearRowId = 'ClearTableRow',
            intervall,
            self = $(this);

        //Binden aller wichtigen Events
        function bindEvents() {
            $('#' + config.searchFieldId).on('keyup', setFilterStringEvent);

            //Das Event für Speichern des Inhaltes nur dann anwenden wenn auch localStorage vom Browser unterstüzt wird.
            if (supportLocalStorage) {
                $('#' + config.searchFieldId).on('keyup', saveSearchStringEvent);
            }

            //Wenn sich der Text z.b. über einen anderne button ändert und nicht per keyup,
            //muss das Eingabefeld trotzdem überprüft werden
            intervall = setInterval(textChangedEvent, 800);
        }

        //Wenn sich der TExt durch ein externen Event ändert, dann überprüft, diese Funktion ob sich der Text
        //in unserem Textfeld geändert hat
        function textChangedEvent() {
            var input = $('#' + config.searchFieldId),
                changed = false;

            //Wenn noch kein Attribut vorhanden ist, eines anlegen
            if (!input.attr('data-oldValue')) {
                input.attr('data-oldValue', input.val());
                //merken ob sich etwas geändert hat, denn die Filter sollen ja nur aufgerufen werden
                //wenn sich etwas außerhalb des normalen keyUp event geändert hat. und wenn das Eingabefeld
                //leer ist, dann denkt jQuery das auch kein Attribut vorhanden ist.
                changed = input.val().length;
            } else {
                //Wenn sich der aktuelle wert zum alten geändert hat, dann muss der Filter neu gesetzt werden
                if (input.attr('data-oldvalue') !== input.val()) {
                    changed = true;
                    input.attr('data-oldvalue', input.val());
                    saveSearchStringEvent(); //den Localstorage anpassen
                }
            }

            //Wenn sich etwas geändert hat die Query anpassen und den Filter auslösen
            if (changed) {
                config.defaultQuery = input.val().toLowerCase();
                filter();
            }
        }

        //Erstellen des Inputfeldes in dem der Suchstring angegeben werden kann
        function createSearchControl() {
            //Das label mit dem namen vor das Eingabefeld setzten
            if (config.searchFieldName.length > 0) {
                $('<label>',
                    {
                        text: config.searchFieldName,
                        class: 'searchLabel'
                    }).appendTo(config.searchField);
            }

            //Input für die Suche erstellen
            $('<input>',
                {
                    type: 'text',
                    name: config.searchFieldId,
                    placeholder: config.searchfieldPlaceholderText, //HTML 5
                    id: config.searchFieldId
                }).appendTo(config.searchField);

            //Label erstellen in dem die Anzahl der Ergebnisse angezeigt wird.
            $('<label>', {
                class: 'labelErgebnisse'
            }).appendTo(config.searchField);

            //Das label direkt aus dem DOM suchen, damit später einfach das Ergebnis gesetzt werden kann
            anzErgebnisseLabel = $('.labelErgebnisse');

            //den Inhalt des Eingabefeldes laden aus dem localstorage
            loadSearchString();
        }

        //setzten des Filters für die Tabellendaten (Timeout)
        function setFilterStringEvent() {
            //das $(this) in diesem falle auf das Inputfeld verweißt,
            //da es sich um eine funktion handelt die vom Eventhandler aufgerufen wird ist this das Inputcontrol
            input = $(this);
            //Den aktuellen Wert in unsere data Attribut eintragen für das textChangedEvent wichtig
            input.attr('data-oldvalue', input.val());

            if (input.val().length < config.minSearchLength) {
                //Alle Rows anzeigen, da der Filter erst ab 2 Eingaben ausgeführt wird, müssen hier immer alle Rows angezeigt werden
                rows.show();
                anzErgebnisseLabel.text(' (' + rows.length + '/' + rows.length + ')');
            }

            clearTimeout(timer);
            //Nicht gleich Filtern, erst wenn mind. 3 Zeichen eingegeben wurden und wenn 400ms vorbei sind.
            self.timer = (input.val().length >= config.minSearchLength) && setTimeout(function () {
                //Setzten des Filters
                config.defaultQuery = input.val().toLowerCase();
                //Die Filtermethode heraussuchen
                filter();
            }, 300);
        }

        //Filtern der werte für die Tabelle
        function filter() {
            //alle worte nach denen gesucht werden soll heraussuchen und vorher mit \W noch alle Sonderzeichen aus dem String entfernen,
            //damit der Regexp nicht "verfälscht" werden kann. ACHTUNG entfernt auch Umlaute!
            var regexp = $.trim(config.defaultQuery.replace(/[_\W]+/g, ' ')).split(' '),
                rowsHide = [], //Array initialisieren in dem angegeben wird welche Rows nicht angezeigt werden
                rowsShow = []; //Array initialisieren in dem agengeben wird welche Rows angezeigt werden sollen

            //Den Rexexp Ausdruck für jedes einzelne Wort bereits zusammenbauen
            $.each(regexp, function (index, word) {
                regexp[index] = '/' + word + '/ig';
            });

            //Die Row für eine ergebnislose suche aus der Tabelle entfernen
            $('#' + clearRowId).remove();

            //Alle Rows der Tabelle durchgehen
            $.each(rows, function (index, row) {
                //den Ganzen Text aus einer Zeile "extrahieren und dann in diesem Text nach dem passenden String suchen
                if (!checkTextWithRegexp($('*', row).text(), regexp)) {
                    //Jede Row die nicht den Regeln entspricht dem array hinzufügen dessen  rows ausgeblendet werden
                    rowsHide.push(row);
                } else {
                    //Alle Rows dem Array hinzufügen die nicht versteckt werden müssen
                    rowsShow.push(row);
                }
            });

            //Das Hide und Show auf das gesammte Array anwenden und nicht auf jede Zeile einzeln
            //ist schneller, als wenn jeder DOM Zugriff einzeln gemacht wird.
            $(rowsHide).hide();
            $(rowsShow).show();

            //Prüfen ob alle Rows ausgeblendet wurden und somit kein Suchergebnis gefunden wurde
            //Wird nur gemacht, wenn es sich nur um eine Tabelle auf der Seite handelt! (bei mehreren Tabellen
            //müsste sonst alles angepasst werden, da man wissen muss bei welcher Tabelle keine Ergebnisse gefunden wurden)
            if (rowsHide.length === rows.length && self.length === 1) {
                //Dann eine Extra Zeile einfügen die anzeigt, das keine Ergebnisse gefunden wurden
                $('tbody', self).append(CreateClearTableRow());
                anzErgebnisseLabel.text(' (0/' + rows.length + ')');
            } else {
                //Anzeigen wieviele Suchergebnisse gefunden wurden
                anzErgebnisseLabel.text(' (' + rowsShow.length + '/' + rows.length + ')');
            }
        }

        //Den übergebenen Text überprüfen ob die Worte darin vorkommen
        function checkTextWithRegexp(checkText, regexp) {
            var count = 0;

            for (var i = 0, len = regexp.length; i < len; i++) {
                //Der Regex ausdruck muss erst noch "umgewandelt" werden mit eval, damit dieser auch als Regex Ausdruck erkannt werden kann.
                if (checkText.match(eval(regexp[i]))) {
                    //jedesmal wenn ein Wort gefunden wurde was gesucht wird hochzählen, wenn
                    //alle worte gefunden wurden, dann kann mit der Zeile aufgehört werden
                    if (count++ === len) return true;
                } else {
                    //Wenn schon das erste wort nicht gefunden wurde, dann direkt false zurück geben
                    return false;
                }
            }

            //Der gesucht String für eine Zeile stimmt nur dann, wenn auch die Anzahl
            //von count der anzahl der Worte entspricht die gesucht wurden.
            if (count === regexp.length) return true;

            return false;
        }

        //erstellt eine "Leere" Table Row, mit einem Text, das keine Ergebnisse gefunden wurden
        function CreateClearTableRow() {
            var clearRow = $('<tr>', {
                id: clearRowId
            }).append($('<td>', {
                text: config.ClearTableRowText,
                colspan: 1000
            }));

            return clearRow;
        }

        //Nutzen von LocalStorage aus HTML 5 um den Inhalt des aktuellen Filters zu speichern
        function saveSearchStringEvent() {
            if (supportLocalStorage()) {
                //Den wert im localstorage ablegen, für den Key wird hier die aktuelle Seite mit verwendet.
                //damit wenn die suche auf unterschiedlichen Seiten eingesetzt wird, diese auch auf jeder Seite individuell gespeichert wird.
                localStorage['Search_' + currentUrl] = $('#' + config.searchFieldId).val();
            }
        }

        //Laden von Inhalt für das Suchfeld auf der aktuellen Seite
        function loadSearchString() {
            //Nur laden, wenn auch localstorage unterstüzt wird
            if (supportLocalStorage) {
                //Den wert aus dem Localstorage für die aktuelle Seite in das Suchfeld schreiben
                $('#' + config.searchFieldId).val(localStorage['Search_' + currentUrl]);
                //und den Filter direkt einmal ausführen, aber den Context des Suchefeldes übergeben, damit auch this stimmt
                setFilterStringEvent.call($('#' + config.searchFieldId));
            }
        }

        //Prüfen ob HTML5 LocalStorage zur Verüfgung steht.
        function supportLocalStorage() {
            try {
                return 'localStorage' in window && window.localStorage !== null;
            } catch (e) {
                return false;
            }
        }

        //Prüfen ob überhaupt Elemente gefunden wurden mit dem Selector mit dem das Element aufgerufen wurde.
        if (this.length > 0) {
            //Alle Rows heraussuchen die gefiltert werden sollen
            rows = $('tbody tr', this);
            $(config.searchField).show();
            //Darf nur einmalig ausgeführt werden, da wir ja nur ein Eingabefeld benötigen
            createSearchControl();
            bindEvents();
        }
    };

})(jQuery, window, document);


//CLEARINPUTTEXT PLUGIN
(function ($, window, document, undefiend) {

    $.fn.clearInputText = function (opt) {
        var defaultsOptions = {
            clearSpanCss: 'clearSpan', //CSS name für das Span welches angelegt wird für das Kreuz
            crossPositionLeft: -20, //Die Position Left für das Kreuz
            crossFadeSpeed: 300, //Die Geschwindigkeit mit der das Delete Kreuz ein oder ausgeblendet werden soll
            crossColor: "#666"
        },
                options = $.extend(defaultsOptions, opt),
                self = this;

        //Init Funktion für das Plugin
        (function init() {
            //Berechnen der Margin für das Input Feld
            // immer noch 5 Px dazu addieren, damit der Text nicht direkt am Cross aufhört.
            var margin = Math.abs(options.crossPositionLeft) + 5;

            //Über die Elemente iterieren die übergeben wurden
            $.each(self, function () {
                var control = createDeleteControl();
                //Da das Control nur angezeigt werden soll, wenn sich Text im Input Feld befindet,
                //vorher prüfen ob bereits Text enthalten ist oder das Control deaktiviert wurde
                if ($(this).val().length > 0 && !$(this).is(':disabled')) {
                    control.show(); // Das Control anzeigen
                }

                //Das Span hinter dem Input Feld im DOM Einfügen(gleiche Ebene)
                $(this).after(control);
            });

            //Nicht in der Schleife machen, sondern in einer DOM Aktion erledigen, da schneller 
            //padding-right hinzufügen, wenn der Text länger ist, das er nicht das Cross "verdeckt"
            self.attr('style', 'padding-right:' + margin + 'px;');
            //Ein Event dem aktuellen input Control hinzufügen, welches das Kreuz einblendet wenn Text
            //eingegeben wird, bzw. ausblendet wenn keiner mehr vorhanden ist.
            self.on('keyup', toggleDeleteControlEvent);

            addStyles();
            bindEvents();
        })();

        //Events binden
        function bindEvents() {
            //Wenn auf unser Kreuz geklickt wird, dann die passende Funktion aufrufen, die den Text "entfernt"
            $('.' + options.clearSpanCss).on('click', clearInputTextEvent);
        }

        //Fügt die wichtigsten Styles direkt dem Header hinzu
        function addStyles() {
            $("<style> ." + options.clearSpanCss + ' { font-weight: bold; position: relative; cursor: pointer; left:' +
                    options.crossPositionLeft + 'px; color: "' +
                    options.crossColor + '" }</span>')
                    .appendTo('head');
        }

        //Erstellen des passenden Span Tags, welches hinter den Input Feldern eingefügt werden soll
        function createDeleteControl() {
            return $('<span>', {
                class: options.clearSpanCss,
                text: 'X',
                style: 'display:none' //Standardmäßig ausblenden
            });
        }

        //Löscht den Input Text aus dem jeweiligen Input Feld.
        function clearInputTextEvent() {
            //Self ist hier das Span mit dem X auf das geklickt wurde um den Text zu entfernen.
            self = $(this);

            //Prüfen ob das Control selbst diabled ist, dann keinen Text entfernen
            if (!self.is(':disabled')) {
                //Das Input davor heraussuchen und den Inhalte "entfernen"
                self.prev('input').val('');
            }

            //das Kreuz selbst ausblenden
            self.fadeOut(options.crossFadeSpeed);
        }

        //blendet das Kreuz zum Entfernen ein oder aus, je nachdem ob sich Text im Feld befindet oder nicht.
        function toggleDeleteControlEvent() {
            //Self ist hier das Input Feld in dem der Text eingegeben wird.
            self = $(this);
            //Das input feld überprüfen, ob ein Wert enthalten ist, und wenn ja 
            //das Kreut zum entfernen des Inhaltes einblenden, sonst ausblenden
            if (self.val().length > 0) {
                self.next('span').fadeIn(options.crossFadeSpeed);
            } else {
                self.next('span').fadeOut(options.crossFadeSpeed);
            }
        }
    };
})(jQuery, window, document);